home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Software Vault: The Gold Collection
/
Software Vault - The Gold Collection (American Databankers) (1993).ISO
/
cdr47
/
pctuto.zip
/
DISK3.EXE
/
lha
/
CHAP16.DOC
< prev
next >
Wrap
Text File
|
1990-07-20
|
18KB
|
448 lines
170
CHAPTER 16 - LONG SIGNED MULTIPLICATION AND DIVISION
Now that you have some subroutines under your belt, it is time to
get back to multiple word arithmetic. This was put on the back
burner because we needed to negate long numbers and it is more
efficient to do that as a subroutine. First, let's negate a long
number and then put the parts together.
To negate a number you complement it, then add 1. It looks like
this:
NUMBER_LENGTH EQU 4
variable1 dq ?
mov si, offset variable1
mov cx, NUMBER_LENGTH
not_loop:
not WORD PTR [si]
add si, 2
loop not_loop
mov si, offset variable1
mov cx, NUMBER_LENGTH
stc ; set carry flag
add_loop:
adc WORD PTR [si], 0
inc si
inc si
loop add_loop
This is straightforward. First negate, then add 1. The first add
will add 1 because the carry flag is set. If there is a carry
out, it will be taken care of in the next word with ADC (we add
nothing but the carry). We can make this more compact and
efficient with:
mov si, offset variable1
mov cx, NUMBER_LENGTH
stc ; set carry flag
negate_loop:
not WORD PTR [si]
adc WORD PTR [si], 0
inc si
inc si
loop negate_loop
Neither NOT nor INC effect CF, the carry flag, so the correct CF
value will be propagated through the whole long number.
When we do negation during our multiplication, the multiplicand
will be a 4 word negation while the result will be a 5 word
______________________
The PC Assembler Tutor - Copyright (C) 1989 Chuck Nelson
Chapter 16 - Multiple Word Arithmetic III 171
_________________________________________
negation, so we will pass the length as a parameter. The call in
C would look like this:
negate_it ( &number, length ) ; {1}
On entry, the stack will look like this:
length bp + 6
address bp + 4
old IP bp + 2
bp -> old BP bp + 0
Here is the entire subroutine:
; - - - - - START SUBROUTINES BELOW THIS LINE
negate_it proc near
NUMBER_LENGTH EQU [bp+6]
NUMBER_ADDRESS EQU [bp+4]
push bp
mov bp, sp
PUSHREGS cx, si
mov si, NUMBER_ADDRESS
mov cx, NUMBER_LENGTH
stc ; set carry flag
negate_loop:
not WORD PTR [si]
adc WORD PTR [si], 0
inc si
inc si
loop negate_loop
POPREGS cx, si
pop bp
ret ; calling routine adjusts stack
negate_it endp
; - - - - - END SUBROUTINES ABOVE THIS LINE
Well, so far we have the negation routine and the unsigned
multiplication and division routines. What else is necessary?
Only the main program, and here it is for multiplication:
; - - - - - START DATA BELOW THIS LINE
multiplicand dq ?
multiplier dw ?
result dt ?
result_sign_flag db ?
; - - - - - END DATA ABOVE THIS LINE
____________________
1. For you non-C people, the '&' stands for the address.
The PC Assembler Tutor 172
______________________
; - - - - - START CODE BELOW THIS LINE
outer_loop:
mov result_sign_flag, 0 ; assume positive
mov ax, offset multiplicand
call get_signed_8byte
test WORD PTR multiplicand + 6, 8000h ; is it negative ?
jz get_next_number
mov ax,4 ; negate 4 word number
push ax
mov ax, offset multiplicand
push ax
call negate_it
add sp, 4 ; clear 2 pushes off stack
not result_sign_flag ; reverse sign of result
get_next_number:
call get_signed ; get signed multiplier
mov multiplier, ax
test ax, 8000h ; is it negative
jz do_the_multiplication
neg multiplier ; negate
not result_sign_flag ; reverse sign of result
do_the_multiplication:
mov ax, offset result
push ax
mov ax, multiplier ; the number, not the address
push ax
mov ax, offset multiplicand
push ax
call multiply_it
add sp, 6 ; clear 3 pushes off stack
; is the result negative?
test result_sign_flag, 0FFh ; 1111 1111 mask
jz print_it
mov ax, 5 ; 5 word result
push ax
mov ax, offset result
push ax
call negate_it
add sp, 4 ; clear 2 pushes off stack
print_it:
mov ax, WORD PTR result + 8 ; top two bytes
call print_hex
mov ax, offset result ; the rest of result
call print_signed_8byte
jmp outer_loop
; - - - - - END CODE ABOVE THIS LINE
The driver routine gets an 8 byte signed number. If the number is
Chapter 16 - Multiple Word Arithmetic III 173
_________________________________________
negative it negates the number (to make it positive) and switches
the sign of the result_sign_flag. The sign flag will either be
00h for positive or FFh for negative. It then gets a two byte
signed number. If it is negative, the routine negates it and
switches the sign flag. At this point both numbers are positive,
so it calls the unsigned multiplication routine. At the end, it
checks the result_sign_flag to see if the result should be
positive or negative. If it should be negative, the routine calls
negate_it one more time. Finally, the routine prints the number.
The hex portion will be 0000 for positive or FFFF for negative
unless the value is larger than an 8 byte signed number can hold,
at which point the value of the 8 byte signed number will be
incorrect.
Here's the unsigned multiplication routine which has been turned
into a subroutine:
; - - - - -
multiply_it proc near
RESULT_ADDRESS EQU [bp+8]
MULTIPLIER_VALUE EQU [bp+6]
MULTIPLICAND_ADDRESS EQU [bp+4]
push bp
mov bp, sp
PUSHREGS ax, bx, cx, dx, si, di
mov si, MULTIPLICAND_ADDRESS ; load pointers
mov bx, RESULT_ADDRESS
mov cx, 4 ; number of words
sub di,di ; clear di
mult_loop:
mov ax, [si] ; multiplicand to ax
mul WORD PTR MULTIPLIER_VALUE
add ax, di ; add high word from last multiplication
jnc store_result
inc dx
store_result:
mov [bx], ax ; store 1 word of result.
mov di, dx ; save high word for next multiplication
add si, 2 ; increment pointers
add bx, 2
loop mult_loop
mov [bx], di ; move last word of result
POPREGS ax, bx, cx, dx, si, di
pop bp
ret ; calling routine adjusts stack
multiply_it endp
; - - - - - - - - - - -
Draw a picture of the stack to verify that the EQU values are
The PC Assembler Tutor 174
______________________
correct. The multiplication and the negation subroutines go in
the subroutine section of SUBTEMP1.ASM. The driver routine is the
main routine. If you don't remember how this multiplication
routine works, go back to the chapter on unsigned multiple word
multiplication since the code is the same.
DIVISION
Division is the same situation. We need a driver routine, but the
division itself will be the unsigned division. In division, the
remainder is the same sign as the dividend, and the sign of the
quotient is (dividend_sign XOR divisor_sign). If both signs are
the same, the quotient is positive; if the signs are different
the quotient is negative. Here's the driver routine:
; - - - - - START DATA BELOW THIS LINE
dividend dq ?
divisor dw ?
quotient dq ?
remainder dw ?
quotient_sign_flag db ?
remainder_sign_flag db ?
; - - - - - END DATA ABOVE THIS LINE
; - - - - - START CODE BELOW THIS LINE
outer_loop:
mov quotient_sign_flag, 0 ; assume positive
mov remainder_sign_flag, 0
mov ax, offset dividend
call get_signed_8byte
test WORD PTR (dividend + 6), 8000h ; is it negative?
jz get_next_number
mov ax,4 ; negate 4 word number
push ax
mov ax, offset dividend
push ax
call negate_it
add sp, 4 ; adjust stack
not quotient_sign_flag ; switch sign of quotient
mov remainder_sign_flag, 0FFh ; remainder is negative
get_next_number:
call get_signed
mov divisor, ax
test ax, 8000h ; is it negative
jz do_the_division
neg divisor
not quotient_sign_flag ; switch sign of quotient
do_the_division:
mov ax, offset remainder
push ax
Chapter 16 - Multiple Word Arithmetic III 175
_________________________________________
mov ax, offset quotient
push ax
mov ax, divisor ; the number, not the address
push ax
mov ax, offset dividend
push ax
call divide_it
add sp, 8 ; clear 4 pushes off stack
; are the remainder and quotient negative?
test remainder_sign_flag, 0FFh
jz test_the_quotient
neg remainder
test_the_quotient:
test quotient_sign_flag, 0FFh ; 1111 1111 mask
jz print_it
mov ax, 4 ; 4 word result
push ax
mov ax, offset quotient
push ax
call negate_it
add sp, 4 ; clear 2 pushes off stack
print_it:
mov ax, offset quotient
call print_signed_8byte
mov ax, remainder
call print_signed
jmp outer_loop
; - - - - - END CODE ABOVE THIS LINE
We get the dividend and check the sign. If it is negative, we (1)
negate the number, (2) switch the sign of the quotient, and (3)
set the remainder sign flag to negative. We get the divisor,
check for negative; if it is negative we negate it and switch the
sign of the quotient. We now have two unsigned numbers and do
unsigned division. After division, both the quotient and
remainder are adjusted for sign.
The division routine is the same as the unsigned routine before
except it is now a subroutine:
; - - - - - - - - ENTER SUBROUTINE BELOW THIS LINE
divide_it proc near
REMAINDER_ADDRESS EQU [bp+10]
QUOTIENT_ADDRESS EQU [bp+8]
DIVISOR_VALUE EQU [bp+6]
DIVIDEND_ADDRESS EQU [bp+4]
push bp
mov bp, sp
PUSHREGS ax, bx, cx, dx, si, di
The PC Assembler Tutor 176
______________________
mov si, DIVIDEND_ADDRESS
mov bx, QUOTIENT_ADDRESS
add si, 6 ; start at the top word
add bx, 6
mov di, WORD PTR DIVISOR_VALUE
mov cx, 4 ; number of words
sub dx, dx ; clear dx for first division
division_loop:
mov ax, [si] ; dividend word to ax
div di
mov [bx], ax ; word of result to quotient
sub si, 2 ; decrement the pointers
sub bx, 2
loop division_loop
mov bx, REMAINDER_ADDRESS ; store remainder
mov [bx], dx
POPREGS ax, bx, cx, dx, si, di
pop bp
ret ; calling routine adjusts the stack
divide_it endp
; - - - - - - - - ENTER SUBROUTINE ABOVE THIS LINE
Draw a picture of the stack to verify that the EQU statements are
correct for a NEAR routine. The division and negation subroutines
go in the SUBROUTINES section of SUBTEMP1.ASM. The driver is the
main program. If you don't remember how this division works, go
back to the division chapter and look it over. Try out a few
numbers to make sure that it is working the way it should.
DATA INTEGRITY
One thing that may have been annoying some of you is that when
the programs sent us numbers for multiplication and division we
sometimes negated them, effectively changing the data in memory,
but never changed them back when we were done. In an operational
subroutine, you would have to do it differently. The logic would
be:
NUMBER NEGATIVE? no everything's o.k.
yes
make copy
negate
reset pointer
If the number is positive we won't change it. If the number is
negative, we make a copy, negate the copy and use the copy for
the operation.